5. 실행 환경 작성에 사용하는 기반 기술

실행 환경 작성에 사용하는 기반 기술

컨테이너가 호스트와 격리된 실행 환경을 만들기 위해 리눅스 커널이 제공하는 네임스페이스cgroup 기능을 사용함.

용어 정리

  • 격리(Isolation): 프로세스가 다른 프로세스나 시스템 자원에 접근하지 못하도록 분리하는 것. 컨테이너의 핵심 개념
  • 리눅스 커널(Linux Kernel): 운영체제의 핵심 부분으로, 하드웨어와 소프트웨어 사이를 중재하며 프로세스, 메모리, 파일시스템 등을 관리
  • 네임스페이스(Namespace): 프로세스가 볼 수 있는 시스템 자원을 격리하는 Linux 커널 기능
  • cgroup(Control Groups): 프로세스의 자원(CPU, 메모리 등) 사용량을 제한하는 Linux 커널 기능


1. 네임스페이스 (Namespace)

어떤 프로세스에서 조작할 수 있는 리소스를 다른 프로세스와 격리할 수 있는 기능임. 컨테이너는 일반적으로 여러 네임스페이스를 조합해서 실행 환경을 작성함.

네임스페이스 종류

네임스페이스 설명 격리 대상
PID 프로세스 ID 격리 프로세스 목록, PID 번호
Mount 마운트 포인트 격리 파일시스템 마운트 목록
Network 네트워크 리소스 격리 네트워크 장치, IP, 포트
UTS 호스트명 격리 hostname, domainname
IPC 프로세스 간 통신 격리 공유 메모리, 세마포어, 메시지 큐
User 사용자/그룹 격리 UID/GID 매핑, 권한
용어 정리

  • PID(Process ID): 운영체제가 각 프로세스를 식별하기 위해 부여하는 고유 번호
  • 마운트 포인트(Mount Point): 파일시스템을 디렉터리 트리에 연결하는 지점. 예: /mnt/usb
  • UTS(Unix Time-sharing System): 호스트명과 도메인명을 관리하는 시스템 식별자
  • IPC(Inter-Process Communication): 프로세스 간 데이터를 주고받는 통신 메커니즘. 공유 메모리, 세마포어, 메시지 큐 등
  • UID/GID: User ID/Group ID. 사용자와 그룹을 식별하는 숫자 값

네임스페이스 격리 구조

┌─────────────────────────────────────────────────────────────┐
│                     호스트 (Host)                            │
│  ┌─────────────────┐  ┌─────────────────┐                   │
│  │   프로세스 A      │  │   프로세스 B      │                   │
│  │   PID: 1234     │  │   PID: 5678     │                   │
│  └─────────────────┘  └─────────────────┘                   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │              컨테이너 (격리된 네임스페이스)               │   │
│  │  ┌─────────────────┐  ┌─────────────────┐           │   │
│  │  │   프로세스 C      │  │   프로세스 D      │           │   │
│  │  │   PID: 1        │  │   PID: 2        │           │   │
│  │  │  (호스트: 9999)  │  │  (호스트: 10000) │           │   │
│  │  └─────────────────┘  └─────────────────┘           │   │
│  │  • 호스트 프로세스 보이지 않음                          │   │
│  │  • PID 1부터 새로 부여                                │   │
│  │  • 독립된 네트워크 스택                                │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘

2. unshare 명령어로 격리 환경 만들기

unshare 명령어를 사용해서 도커나 runc가 만드는 컨테이너처럼 호스트와 격리된 실행 환경을 작성할 수 있음.

용어 정리

  • unshare: 새로운 네임스페이스를 생성하고 그 안에서 프로그램을 실행하는 Linux 명령어
  • chroot(Change Root): 프로세스의 루트 디렉터리를 변경하는 명령어. 파일시스템 격리의 기초 기술
  • fork: 현재 프로세스를 복제하여 새로운 자식 프로세스를 생성하는 시스템 콜

기본 사용법

# 새로운 네임스페이스에서 셸 실행
unshare -fpmn chroot bundle/rootfs /bin/sh

# 격리된 환경에서 OS 정보 확인
cat /etc/os-release

unshare 옵션

옵션 의미 설명
-f fork 새 프로세스로 명령어 실행
-p PID namespace PID 네임스페이스 생성
-m Mount namespace 마운트 네임스페이스 생성
-n Network namespace 네트워크 네임스페이스 생성

3. PID Namespace

-p 옵션으로 PID 네임스페이스를 생성하면 새로운 네임스페이스에서는 PID가 1부터 다시 부여됨.

용어 정리

  • procfs(/proc): 커널이 제공하는 가상 파일시스템. 프로세스 정보, 시스템 상태 등을 파일 형태로 제공
  • PID 1(init 프로세스): 시스템의 첫 번째 프로세스. 모든 프로세스의 조상이며, 고아 프로세스를 거둬들이는 역할 수행

PID 격리 동작

# 새 네임스페이스에서 procfs 마운트 후 프로세스 확인
mount -t proc proc /proc
ps -Ao pid,args
# 출력 예시 - 호스트 프로세스는 보이지 않음
  PID COMMAND
    1 /bin/sh      ← unshare로 실행한 셸이 PID 1
    2 ps -Ao pid,args

이중 PID 구조

새 네임스페이스 안에서 생성된 프로세스는 최소 2개의 PID를 가짐:

┌──────────────────────────────────────────────────┐
│  호스트 네임스페이스                               │
│    sleep 12345  →  PID: 9876                    │
└──────────────────────────────────────────────────┘
                        ↑
                    같은 프로세스
                        ↓
┌──────────────────────────────────────────────────┐
│  컨테이너 네임스페이스                             │
│    sleep 12345  →  PID: 5                       │
└──────────────────────────────────────────────────┘
# 네임스페이스 내부에서 실행
sleep 12345 &
ps -Ao pid,args | grep "sleep 12345"
# 출력: 5 sleep 12345

# 호스트에서 실행
ps -Ao pid,cmd | grep "sleep 12345"
# 출력: 9876 sleep 12345

4. Mount Namespace

-m 옵션으로 마운트 네임스페이스를 생성하면 마운트/언마운트 작업이 호스트에 영향을 주지 않음.

마운트 격리 동작

┌────────────────────────────────────────────────────────┐
│  호스트                                                 │
│  /proc → 호스트 procfs                                 │
│  bundle/rootfs/proc → (빈 디렉터리)                    │
└────────────────────────────────────────────────────────┘

┌────────────────────────────────────────────────────────┐
│  컨테이너 (Mount Namespace)                            │
│  /proc → 컨테이너 procfs  ← 호스트에서 보이지 않음      │
└────────────────────────────────────────────────────────┘

runc의 루트 디렉터리 변경

용어 정리

  • pivot_root: 현재 루트 파일시스템을 새로운 위치로 이동하고, 다른 파일시스템을 새 루트로 설정하는 시스템 콜. chroot보다 보안성이 높음
  • 시스템 콜(System Call): 사용자 공간 프로그램이 커널 기능을 요청하는 인터페이스. 예: read, write, clone, mount

shared subtree

네임스페이스끼리 마운트/언마운트 이벤트를 공유할 수 있는 기능도 있음.

용어 정리

  • shared subtree: 마운트 네임스페이스 간에 마운트 이벤트를 전파하거나 격리하는 기능. shared, private, slave, unbindable 네 가지 모드 존재


5. Network Namespace

-n 옵션으로 네트워크 네임스페이스를 생성하면 네트워크 장치가 호스트와 격리됨.

네트워크 격리 동작

# 컨테이너 내부에서 실행
ip a
# 출력: loopback만 표시, 호스트 네트워크 장치 없음

# 호스트에서 실행
ip a
# 출력: eth0, docker0 등 모든 네트워크 장치 표시

컨테이너 네트워킹 구성

도커나 CNI 플러그인이 새 네트워크 네임스페이스에 가상 네트워크 인터페이스를 생성해서 통신 가능하게 함.

용어 정리

  • CNI(Container Network Interface): 컨테이너 네트워킹을 위한 표준 인터페이스. 쿠버네티스 등에서 네트워크 플러그인 연결에 사용
  • veth(Virtual Ethernet): 한 쌍으로 생성되는 가상 네트워크 인터페이스. 한쪽에서 보낸 패킷이 다른 쪽으로 전달됨
  • 브릿지(Bridge): 여러 네트워크 인터페이스를 하나의 네트워크 세그먼트로 연결하는 가상 스위치. docker0가 대표적
  • loopback(lo): 자기 자신과 통신하기 위한 가상 네트워크 인터페이스. IP 주소 127.0.0.1

┌─────────────────────────────────────────────────────────┐
│                        호스트                            │
│   ┌─────────┐      ┌─────────┐      ┌─────────┐        │
│   │  eth0   │      │ docker0 │      │  vethXXX │       │
│   │ (물리)   │      │ (브릿지) │←────→│ (가상)   │       │
│   └─────────┘      └─────────┘      └────┬────┘        │
│                                          │              │
│        ┌─────────────────────────────────┼──────┐      │
│        │     컨테이너 (Network NS)        │      │      │
│        │                          ┌──────┴────┐ │      │
│        │                          │   eth0    │ │      │
│        │                          │ (가상)    │ │      │
│        │                          └───────────┘ │      │
│        └────────────────────────────────────────┘      │
└─────────────────────────────────────────────────────────┘

추상 유닉스 소켓 격리

소켓 유형 경로 예시 접근 관리
일반 유닉스 소켓 /run/docker.sock 파일시스템 권한으로 관리
추상 유닉스 소켓 @/tmp/.socket 경로 없음, 관리 어려움
용어 정리

  • 유닉스 소켓(Unix Socket): 같은 시스템 내 프로세스 간 통신에 사용하는 소켓. 파일시스템 경로로 식별
  • 추상 유닉스 소켓(Abstract Unix Socket): 파일시스템에 존재하지 않는 유닉스 소켓. @로 시작하며, 파일 권한으로 접근 제어 불가

네트워크 네임스페이스로 추상 유닉스 소켓을 격리할 수 있음:

# 호스트에서 실행 - 추상 소켓 목록 표시
grep -ao '@.*' /proc/net/unix

# 컨테이너에서 실행 - 출력 없음 (격리됨)
grep -ao '@.*' /proc/net/unix

6. cgroup (Control Groups)

프로세스가 사용할 수 있는 리소스를 제한하는 기능임.

용어 정리

  • 컨트롤러(Controller): cgroup에서 특정 자원 유형을 관리하는 모듈. cpu, memory, io 등 각각의 자원별로 존재
  • 계층 구조(Hierarchy): 부모-자식 관계로 구성된 트리 형태의 구조. cgroup은 계층적으로 자원 제한을 상속

cgroup으로 제어 가능한 리소스

컨트롤러 제어 대상
devices 디바이스 파일 접근 권한
cpu CPU 사용량 제한
memory 메모리 사용량 제한
io I/O 대역폭 제한
pids 프로세스 개수 제한

cgroup 계층 구조

/sys/fs/cgroup/
├── cgroup.controllers     ← 사용 가능한 컨트롤러 목록
├── cgroup.procs           ← 소속 프로세스 목록
├── cpu.max                ← CPU 제한 설정
├── memory.max             ← 메모리 제한 설정
│
├── docker/                ← 도커 컨테이너용 cgroup
│   ├── container_id_1/
│   │   ├── cgroup.procs
│   │   └── memory.max
│   └── container_id_2/
│
└── system.slice/          ← systemd 관리 cgroup
    └── unshare_demo.scope/

핵심 특징: 루트에서 말단으로 갈수록 제한이 강해지는 구조


7. cgroup v1 vs v2

버전별 특징 비교

특징 cgroup v1 cgroup v2
파일시스템 구조 컨트롤러마다 분리 단일 통합 계층 구조
프로세스 소속 중간 cgroup 소속 가능 루트 또는 말단만 가능
디바이스 제어 devices.deny 파일 사용 eBPF 프로그램 사용
설계 철학 유연함 (복잡함) 단순함 (일관성)
지원 배포판 Ubuntu 20.04 이하 Ubuntu 22.04+, 최신 배포판
용어 정리

  • eBPF(extended Berkeley Packet Filter): 리눅스 커널 내에서 샌드박스된 프로그램을 실행하는 기술. 네트워크 필터링, 트레이싱, 보안 정책 등에 활용
  • 말단 cgroup(Leaf cgroup): 자식 cgroup이 없는 최하위 cgroup. cgroup v2에서는 프로세스가 말단 cgroup에만 소속 가능

cgroup v1 디렉터리 구조

# Ubuntu 20.04 (cgroup v1)
ls /sys/fs/cgroup/
# 출력: blkio cpu cpuacct cpuset devices freezer memory net_cls ...

컨트롤러마다 별도 디렉터리가 존재함.

cgroup v2 디렉터리 구조

# Ubuntu 22.04 (cgroup v2)
ls /sys/fs/cgroup/
# 출력: cgroup.controllers cgroup.procs cpu.max memory.max io.max ...

cat /sys/fs/cgroup/cgroup.controllers
# 출력: cpuset cpu io memory pids

모든 컨트롤러가 단일 디렉터리에서 관리됨.


8. cgroup v1 디바이스 접근 제한 예시

시나리오

unshare로 만든 격리 환경에서 /dev/sda 디바이스 접근을 차단함.

용어 정리

  • 디바이스 노드(Device Node): /dev 디렉터리 아래의 특수 파일. 하드웨어 장치를 파일처럼 접근할 수 있게 함
  • 블록 디바이스(Block Device): 데이터를 블록 단위로 읽고 쓰는 저장 장치. 예: HDD(/dev/sda), SSD
  • 메이저/마이너 번호(Major/Minor Number): 디바이스를 식별하는 숫자 쌍. 메이저는 드라이버 유형, 마이너는 해당 드라이버 내 개별 장치
  • mknod: 디바이스 노드 파일을 생성하는 명령어
  • hexdump: 파일이나 장치의 내용을 16진수로 표시하는 명령어

제한 전 동작 확인

# 격리 환경 내부에서 디바이스 노드 생성
mknod /dev/sda b 8 0

# 디스크 읽기 가능
hexdump -n 4 /dev/sda
# 출력: 0000000 63eb 0090

cgroup 설정으로 접근 차단

# 1. 새 cgroup 생성
mkdir /sys/fs/cgroup/devices/unshare_demo

# 2. /dev/sda 읽기/쓰기 거부 설정
#    b=블록디바이스, 8:0=메이저:마이너 번호, rw=읽기쓰기 거부
echo "b 8:0 rw" > /sys/fs/cgroup/devices/unshare_demo/devices.deny

# 3. 셸 프로세스를 cgroup에 소속
echo 4423 > /sys/fs/cgroup/devices/unshare_demo/cgroup.procs

제한 후 동작 확인

# 디스크 읽기 불가능
hexdump -n 4 /dev/sda
# 출력: hexdump: /dev/sda: Operation not permitted

도커 컨테이너의 devices cgroup

docker run -it --rm busybox:1.31 /bin/sh

# 컨테이너 내부에서 확인
cat /sys/fs/cgroup/devices/devices.list
# 출력: 허용된 디바이스 목록 표시

9. cgroup v2 디바이스 접근 제한 예시

cgroup v2에서는 eBPF를 사용해서 디바이스 접근을 제어함.

eBPF란?

리눅스 커널의 프로그램 실행 환경으로, 커널 동작을 다양하게 변경/확장할 수 있음:

용어 정리

  • 트레이싱(Tracing): 프로그램 실행 흐름이나 시스템 호출을 추적하고 기록하는 것. 디버깅과 성능 분석에 활용
  • systemd: 리눅스 시스템의 서비스 관리자(init 시스템). 서비스 시작/종료, cgroup 관리 등 수행
  • systemd-run: systemd를 통해 임시로 서비스나 스코프를 생성하여 명령어를 실행하는 도구
  • DeviceAllow: systemd 유닛 설정에서 디바이스 접근 권한을 지정하는 옵션

systemd를 사용한 디바이스 제한

# 제한 전 - 디스크 읽기 가능
mknod /dev/sda b 8 0
hexdump -n 4 /dev/sda
# 출력: 0000000 63eb 0090

# DeviceAllow로 제한 설정하면서 실행환경 생성
# m = 디바이스 파일 생성만 허가 (읽기/쓰기 거부)
systemd-run \
  -p "DeviceAllow=/dev/sda m" \
  --unit=unshare_demo \
  --scope \
  unshare -fpmn chroot bundle/rootfs /bin/sh

# 제한 후 - 디스크 읽기 불가능
mknod /dev/sda b 8 0
hexdump -n 4 /dev/sda
# 출력: hexdump: /dev/sda: Operation not permitted

cgroup 정보 확인

# 생성된 cgroup 확인
systemd-cgls -u unshare_demo.scope

# cgroup 디렉터리 위치
cat /sys/fs/cgroup/system.slice/unshare_demo.scope/cgroup.procs
# 출력: 3640 3641

ps -o pid,args -p "3640 3641"
# 출력:
#   PID ARGS
#  3640 unshare -fpmn chroot bundle/rootfs /bin/sh
#  3641 /bin/sh

10. cgroup namespace와 nsdelegate

cgroup namespace

프로세스에서 cgroup 계층 구조를 한정적인 범위로 보여주는 기능임.

용어 정리

  • cgroup namespace: 프로세스가 볼 수 있는 cgroup 계층 구조를 격리하는 네임스페이스. 컨테이너가 자신의 cgroup을 루트로 인식하게 함
  • nsdelegate: cgroup v2의 마운트 옵션으로, cgroup namespace 경계를 넘는 자원 위임을 제한하는 보안 기능

nsdelegate (cgroup v2)

cgroup v2에 도입된 보안 기능:

기능 설명
프로세스 이동 제한 namespace 내부 프로세스를 다른 cgroup으로 이동 불가
설정 파일 쓰기 제한 namespace 외부에서 설정 변경 제한

요약

┌─────────────────────────────────────────────────────────────┐
│                    컨테이너 격리 기술                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  네임스페이스 (Namespace)         cgroup                     │
│  ━━━━━━━━━━━━━━━━━━━━━         ━━━━━━                      │
│  "무엇을 볼 수 있는가"           "얼마나 쓸 수 있는가"          │
│                                                             │
│  • PID    - 프로세스 격리        • cpu    - CPU 제한         │
│  • Mount  - 파일시스템 격리      • memory - 메모리 제한       │
│  • Network - 네트워크 격리       • io     - I/O 제한         │
│  • UTS    - 호스트명 격리        • devices - 디바이스 제한    │
│  • IPC    - IPC 격리            • pids   - 프로세스 수 제한  │
│  • User   - 사용자 격리                                      │
│                                                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│        컨테이너 런타임 (runc, containerd, CRI-O)             │
│                         ↓                                   │
│         네임스페이스 + cgroup 조합으로 컨테이너 생성           │
│                                                             │
└─────────────────────────────────────────────────────────────┘
개념 역할 구현 방식
네임스페이스 리소스 가시성 격리 unshare, clone() 시스템 콜
cgroup 리소스 사용량 제한 파일시스템 기반 설정
chroot/pivot_root 루트 디렉터리 격리 시스템 콜
eBPF 커널 동작 확장 (v2) 커널 내 프로그램 실행

참고 자료

공식 문서:

심화 자료: